#pragma once

namespace Tmpl {

typedef unsigned int Pixel;

#define RMASK		0x000000FF
#define GMASK		0x0000FF00
#define BMASK		0x00FF0000
#define AMASK		0xFF000000

#define CMASK		0x00FFFFFF
#define AGMASK		0xFF00FF00
#define RBMASK		0x00FF00FF
#define HALVEMASK	0x7F7F7F7F

#define RSHIFT		0
#define GSHIFT		8
#define BSHIFT		16
#define ASHIFT		24

#define CLIPRECT_STACKSIZE		16
#define DEPTHSTK_STACKSIZE		16

// ===========================================================
// Pixel blending functions
// ===========================================================

inline Pixel AlphaBlend(Pixel dst, Pixel src, int srcAlpha8)
{
	const Pixel RBdst = dst & RBMASK, Gdst = dst & GMASK;
	const Pixel RB = RBMASK & (RBdst + ((((src & RBMASK) - RBdst) * srcAlpha8) >> 8));
	const Pixel G = GMASK & (Gdst + ((((src & GMASK) - Gdst) * srcAlpha8) >> 8));
	return (AMASK | RB | G);
}

inline Pixel MergeBlend(Pixel a, Pixel b)
{
	return ((a>>1) & HALVEMASK) + ((b>>1) & HALVEMASK);
}

inline Pixel AddBlend(Pixel a, Pixel b)
{
	const Pixel RBsum = (a & RBMASK) + (b & RBMASK);
	const Pixel Gsum = (a & GMASK) + (b & GMASK);
	const Pixel RB = (RBsum & RBMASK) | (BMASK * (RBsum >> 24)) | (RMASK * ((RBsum >> 8) & 1));
	const Pixel G = (Gsum & GMASK) | (GMASK * (Gsum >> 16));
	return (AMASK | RB | G);
}

inline Pixel SubBlend(Pixel a, Pixel b)
{
	const Pixel Ra = a & RMASK, Rb = b & RMASK;
	const Pixel Ga = a & GMASK, Gb = b & GMASK;
	const Pixel Ba = a & BMASK, Bb = b & BMASK;
	const Pixel R = (Ra < Rb) ? 0 : (Ra - Rb);
	const Pixel G = (Ga < Gb) ? 0 : (Ga - Gb);
	const Pixel B = (Ba < Bb) ? 0 : (Ba - Bb);
	return (AMASK | R | G | B);
}

inline Pixel ScaleBlend(Pixel color, int intensity8)
{
	const Pixel RB = (((color & RBMASK) * intensity8) >> 8) & RBMASK;
	const Pixel G = (((color & GMASK) * intensity8) >> 8) & GMASK;
	return ((color & AMASK) | RB | G);
}

// ===========================================================
// Drawing functions
// ===========================================================

namespace Draw
{
	enum BlendMode {NORMAL, ALPHA, ADD, INVERT};

	extern void Clear();

	// Blending functions
	extern void SetBlendMode(BlendMode newMode = NORMAL);

	// Depth functions
	extern void DepthPush(int depth);
	extern void DepthPop();
	extern int GetDepth();

	// ClipRect functions
	extern void ClipRectAdjust(unsigned int windowWidth, unsigned int windowHeight);
	extern void ClipRectPush(int x1, int y1, int x2, int y2);
	extern void ClipRectPop();

	// Line drawing functions
	extern void LineH(int x, int y, int x2, Pixel color);
	extern void LineH(int x, int y, int x2, Pixel color1, Pixel color2);
	extern void LineV(int x, int y, int y2, Pixel color);
	extern void LineV(int x, int y, int y2, Pixel color1, Pixel color2);
	extern void Line(int x1, int y1, int x2, int y2, Pixel color);
	extern void Line(int x1, int y1, int x2, int y2, Pixel color1, Pixel color2);
	extern void Line(float x1, float y1, float x2, float y2, Pixel color);
	extern void Line(float x1, float y1, float x2, float y2, Pixel color1, Pixel color2);

	// Misc geometrical drawing functions
	extern void Point(int x, int y, Pixel color);
	extern void Box(int x1, int y1, int x2, int y2, Pixel color);
	extern void Bar(int x1, int y1, int x2, int y2, Pixel color);
	extern void BarH(int x1, int y1, int x2, int y2, Pixel colorLft, Pixel colorRgt);
	extern void BarV(int x1, int y1, int x2, int y2, Pixel colorTop, Pixel colorBtm);

	extern void Circle(int x, int y, float radius, int segments, bool fill, Pixel color);
	extern void Circle(float x, float y, float radius, int segments, bool fill, Pixel color);
	extern void CatmullRom(const Vec2f* points, unsigned int pointCount, unsigned int subSegments, Pixel color);
};

// ===========================================================
// Image classes
// ===========================================================

struct ImageUvRect
{
	ImageUvRect() : u0(0), v0(0), u1(1), v1(1) {}
	ImageUvRect(double U0, double V0, double U1, double V1) : u0(U0), v0(V0), u1(U1), v1(V1) {}
	
	double u0, v0, u1, v1;
};

class Image
{
public:
	enum TexFlags
	{
		TEX_FILTER = 1 << 0,
		TEX_MIPMAP = 1 << 1,
		TEX_CLAMP =  1 << 2
	};

	Image() : myHasExternalTexId(false), myHasExternalBuffer(false), mOpenGlTexId(0), mBuffer(0) {}
	~Image() {DestroyPixels(); DestroyTexture();}

	bool Create(const char* imageFile);
	void Create(Pixel* bufferRGBA, int w, int h, bool copyPixels=false);

	bool BuildTex(int texFlags=0, bool destroyPixels=true);
	void BuildTex(unsigned int externalTexId, int w, int h, double u0, double v0, double u1, double v1);

	void DestroyPixels();
	void DestroyTexture();

	// General functionality
	void Draw(int x, int y, Pixel color=0xFFFFFFFF) const;
	void Draw(float x, float y, float w, float h, float rotation, Pixel color=0xFFFFFFFF) const;

	// Member data access
	void SetOrigin(int x, int y)		{mOrigin.Set(x, y);}

	const Vec2i& GetSize() const		{return mSize;}
	const Vec2i& GetOrigin() const		{return mOrigin;}

	const Pixel* GetBuffer() const		{return mBuffer;}
	const ImageUvRect& GetUVs() const	{return mUVs;}
	unsigned int GetTexId() const		{return mOpenGlTexId;}

	bool HasBuffer() const				{return (mBuffer != 0);}
	bool HasTex() const					{return (mOpenGlTexId != 0);}

	static void ForcePowerOfTwoTextures(bool enable);

protected:
	static bool mForcePowerOfTwo;
	bool myHasExternalTexId;
	bool myHasExternalBuffer;
	unsigned int mOpenGlTexId;
	Vec2i mSize, mOrigin;
	ImageUvRect mUVs;
	Pixel* mBuffer;
};

class Tileset
{
public:
	Tileset() {}
	~Tileset() {}

	void Create(const Image& tileSheet, int tileW, int tileH, int spacingW, int spacingH);
	void Destroy();

	const Image& GetTile(int idx) const;
	Image& GetTile(int idx);

	const Vec2i& GetTileSize() const	{return mTileSize;}
	const Vec2i& GetTileSpacing() const	{return mTileSpacing;}
	
	int GetTilesInW() const;
	int GetTilesInH() const;

private:
	Image mTileSheet;
	Vec2i mTileSize, mTileSpacing;
	mutable Vec2i mTileIdx;
};

}; // namespace Tmpl